package com.zzl.redis.rock;

import cn.hutool.core.util.IdUtil;
import com.zzl.redis.entity.RedisLockInfo;
import com.zzl.redis.JedisUtil;
import com.zzl.redis.config.RedisPoolConfig;
import lombok.AllArgsConstructor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicBoolean;

public class LuaRedisLock {
    private static final Logger logger = LoggerFactory.getLogger(LuaRedisLock.class);
    private static final String LUA_SCRIPT_LOCK = "return redis.call('set',KEYS[1],ARGV[1],'NX','PX',ARGV[2])";// 有效期以毫秒为单位
//    private static final String LUA_SCRIPT_LOCK = "return redis.call('set',KEYS[1],ARGV[1],'NX','EX',ARGV[2])";
    private static final String LUA_SCRIPT_UNLOCK = "if redis.call('get',KEYS[1]) == ARGV[1] then return tostring(redis.call('del', KEYS[1])) else return '0' end";

    private static String script_lock_sha ;
    private static String script_unlock_sha ;

    private static JedisPool jedisPool;

    static {
//        JedisShardInfo jedisShardInfo = new JedisShardInfo("127.0.0.1", 6379);
//        singleJedis = JedisUtil.getSingleJedis(jedisShardInfo);
        RedisPoolConfig config = new RedisPoolConfig();
        config.setAddr("127.0.0.1");
        config.setPort(6379);
        config.setAuth("");
        config.setMaxIdle(200);
        config.setMaxActive(1024);
        config.setTestOnBorrow(true);
        config.setTimeOut(10000);
        jedisPool = JedisUtil.getJedisPool(config);
    }

    /**
     * 其实加锁也可以直接用 jedis.setnx 完成
     * @param jedis
     * @param lockKey
     * @param expireTime
     * @param tryTimeout
     * @return
     */
    public static RedisLockInfo tryLock(Jedis jedis, String lockKey, Long expireTime, Long tryTimeout){
        long timestamp = System.currentTimeMillis();
        int tryCount = 0;
        String value  = IdUtil.randomUUID();
        List<String> ages = new ArrayList();
        ages.add(value);
        ages.add(String.valueOf(expireTime));
        while ((System.currentTimeMillis() - timestamp) < tryTimeout) {
            tryCount++;
            try{
//                Object lockResult = jedis.eval(LUA_SCRIPT_LOCK, Collections.singletonList(lockKey), ages);
                Object lockResult = jedis.evalsha(script_lock_sha, Collections.singletonList(lockKey), ages);

                if (null != lockResult && "OK".equals(lockResult)) {
                    return new RedisLockInfo(value,lockKey,expireTime,tryTimeout,tryCount);
                }else {
                    Thread.sleep(50);
                }
            }catch (Exception e){
                e.printStackTrace();
                return null;
            }
        }
        return null;
    }

    public static boolean releaseLock(Jedis jedis,RedisLockInfo redisLockInfo){
        Object releaseResult = jedis.evalsha(script_unlock_sha,Collections.singletonList(redisLockInfo.getRedisKey()),Collections.singletonList(redisLockInfo.getLockId()));
        if(null !=releaseResult && "1".equals(releaseResult)){
            return true;
        }
        return false;
    }

    private static String key1 = "luakey";
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        init(jedisPool);
//        baseTest();
        concurrentTest();
    }


    private static void init(JedisPool jedisPool){
        Jedis jedis = jedisPool.getResource();
        try {
            jedis.scriptFlush();// 清除历史 缓存的  lua脚本
            script_lock_sha = jedis.scriptLoad(LUA_SCRIPT_LOCK);
            logger.info(jedis.scriptExists(script_lock_sha).toString());
            script_unlock_sha = jedis.scriptLoad(LUA_SCRIPT_UNLOCK);
            logger.info(jedis.scriptExists(script_unlock_sha).toString());
        }finally {
            JedisUtil.returnResource(jedisPool,jedis);
        }
    }
    /**
     * 基础功能测试
     */
    private static void baseTest(){
        RedisLockInfo redisLockInfo = tryLock(jedisPool.getResource(),key1,50*1000L,5*1000L);
        logger.info(redisLockInfo.toString());
        boolean flag = releaseLock(jedisPool.getResource(),redisLockInfo);
        redisLockInfo = tryLock(jedisPool.getResource(),key1,50*1000L,50*1000L);
        logger.info(redisLockInfo.toString());
        flag = releaseLock(jedisPool.getResource(),redisLockInfo);
    }



    /**
     * 并发测试
     */
    private static ConcurrentLinkedQueue<RedisLockInfo> redisLockInfos = new ConcurrentLinkedQueue<>();
    private static AtomicBoolean isFinish = new AtomicBoolean(false);
    private static final ThreadPoolExecutor executor = (ThreadPoolExecutor) Executors.newFixedThreadPool(20);

    private static void concurrentTest() throws InterruptedException, ExecutionException {
        LockControlTask lockControlTask = new LockControlTask();
        FutureTask lockControlFutureTask = new FutureTask(lockControlTask);
        Thread lockControlThread = new Thread(lockControlFutureTask);
        lockControlThread.start();

        List<FutureTask> futureTaskList = new ArrayList<>(10000);
        for (int i =0;i<1000;i++){
            LockTask lockTask = new LockTask();
            FutureTask futureTask = new FutureTask(lockTask);
            Thread thread = new Thread(futureTask);
            thread.start();
            futureTaskList.add(futureTask);
            Thread.sleep(5);
        }
        for (FutureTask futureTask : futureTaskList){
            futureTask.get();
        }
        isFinish.set(true);
        lockControlFutureTask.get();

        while (executor.getActiveCount()>0){
            Thread.sleep(500);
        }
        executor.shutdown();
        System.out.println(true);
    }

    public static class LockControlTask implements Callable{

        @Override
        public Object call() throws Exception {
            while (!isFinish.get() || !redisLockInfos.isEmpty() ){
                RedisLockInfo redisLockInfo;
                if (null != (redisLockInfo= redisLockInfos.poll())){
                    executor.submit(new ReleaseTask(redisLockInfo));
                    executor.submit(new ReleaseTask(redisLockInfo));
                    executor.submit(new ReleaseTask(redisLockInfo));
                }else {
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
//                logger.info("{}",redisLockInfo);
            }
            return "";
        }
    }

    public static class LockTask implements Callable{
        @Override
        public Object call() throws Exception {
            try {
                Jedis jedis = null;
                try {
                    jedis = jedisPool.getResource();
                    RedisLockInfo redisLockInfo = tryLock(jedis,key1,50L,200L);
                    if(null!=redisLockInfo){
                        redisLockInfos.add(redisLockInfo);//null 不能插入
//                        logger.info("加锁情况：" +redisLockInfo);// 记录所有的加锁信息
                    }
                    logger.info("加锁情况：" +redisLockInfo);// 记录所有的加锁信息
                }finally {
                    JedisUtil.returnResource(jedisPool,jedis);
                }
                return "";
            }catch (Exception e){
                e.printStackTrace();
            }
            return "";
        }
    }

    @AllArgsConstructor
    public static class ReleaseTask implements Runnable{
        RedisLockInfo redisLockInfo;

        @Override
        public void run() {
            Jedis jedis = jedisPool.getResource();
            try {
                boolean isRelease = releaseLock(jedis,redisLockInfo);
//                if (isRelease){
//                    logger.info("解锁情况：{},{}",isRelease,redisLockInfo);
//                }
                logger.info("解锁情况：{},{}",isRelease,redisLockInfo);
            }finally {
                JedisUtil.returnResource(jedisPool,jedis);
            }
        }
    }
}
